<?php
/**
 * Talents come from diligence, and knowledge is gained by accumulation.
 *
 * @author: 晋<657306123@qq.com>
 * @date: 2019/8/5 23:20
 */

namespace Xin\Filesystem\Adapter\Qiniu;

use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait;
use League\Flysystem\Config;
use Qiniu\Auth;
use Qiniu\Storage\BucketManager;
use Qiniu\Storage\UploadManager;
use Xin\Filesystem\Adapter\AbstractCloudAdapter;
use Xin\Filesystem\FilesystemException;

/**
 * 七牛云
 *
 * @link https://developer.qiniu.com/kodo/sdk/1241/php
 */
class Qiniu extends AbstractCloudAdapter{

	use NotSupportingVisibilityTrait;

	/**
	 * @var \Qiniu\Auth
	 */
	private $auth;

	/**
	 * @var \Qiniu\Storage\UploadManager
	 */
	private $uploadManager;

	/**
	 * @var \Qiniu\Storage\BucketManager
	 */
	private $bucketManager;

	/**
	 * Qiniu constructor.
	 *
	 * @param array $config
	 * @throws \Xin\Filesystem\FilesystemException
	 */
	public function __construct(array $config){
		parent::__construct($config);

		// 初始化签权对象
		$this->auth = new Auth($config['ak'], $config['sk']);

		// 初始化上传管理器
		$this->uploadManager = new UploadManager();

		// 初始化资源管理器
		$this->bucketManager = new BucketManager($this->auth);
	}

	/**
	 * @return \Qiniu\Auth
	 */
	public function getAuth(){
		return $this->auth;
	}

	/**
	 * @return \Qiniu\Storage\UploadManager
	 */
	public function getUploadManager(){
		return $this->uploadManager;
	}

	/**
	 * @return \Qiniu\Storage\BucketManager
	 */
	public function getBucketManager(){
		return $this->bucketManager;
	}

	/**
	 * @inheritDoc
	 */
	public function getUploadToken($path = null, $expires = 3600, $policy = null, $strictPolicy = true){
		return $this->auth->uploadToken(
			$this->config['bucket'], $path, $expires,
			$policy, $strictPolicy
		);
	}

	/**
	 * @inheritDoc
	 */
	public function getPrivateFileUrl($path, $expires = 3600){
		return $this->auth->privateDownloadUrl($path, $expires);
	}

	/**
	 * @param \Qiniu\Http\Error $error
	 * @throws \Xin\Filesystem\FilesystemException
	 */
	protected function throwException($error){
		throw new FilesystemException(
			$error->getResponse()->error,
			$error->getResponse()->statusCode
		);
	}

	/**
	 * @inheritDoc
	 */
	public function write($path, $contents, Config $config){
		$key = $this->applyPathPrefix($path);
		$options = $this->resolveOptions($config);
		$options['returnBody'] = '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","mimeType":"$(mimeType)","imageInfo":$(imageInfo)}';

		$token = $this->getUploadToken(null, 3600, $options);

		//		$mime = 'application/octet-stream';
		$mime = $config->get('mime');

		/** @var $err \Qiniu\Http\Error */
		list($result, $error) = $this->uploadManager->put(
			$token, $key, $contents,
			null, $mime, $path
		);
		if($error !== null){
			$this->lastError = $error->message();
			return null;
		}

		$result['putTime'] = time()."0000000";
		$result = $this->normalizeFileInfo($result);
		$this->fileInfoPool->put($key, $result);

		return $result;
	}

	/**
	 * @inheritDoc
	 * @link http://developer.qiniu.com/docs/v6/api/reference/rs/move.html
	 */
	public function rename($path, $newpath){
		$key = $this->applyPathPrefix($path);
		$newkey = $this->applyPathPrefix($newpath);

		/** @var $error \Qiniu\Http\Error */
		$error = $this->bucketManager->rename(
			$this->config['bucket'], $key, $newkey
		);
		if($error !== null){
			$this->lastError = $error->message();
			return false;
		}

		return true;
	}

	/**
	 * @inheritDoc
	 * @link http://developer.qiniu.com/docs/v6/api/reference/rs/copy.html
	 */
	public function copy($path, $newpath){
		$key = $this->applyPathPrefix($path);
		$newkey = $this->applyPathPrefix($newpath);

		/** @var $error \Qiniu\Http\Error */
		$error = $this->bucketManager->copy(
			$this->config['bucket'], $key,
			$this->config['bucket'], $newkey
		);
		if($error !== null){
			$this->lastError = $error->message();
			return false;
		}

		return true;
	}

	/**
	 * @inheritDoc
	 * @link http://developer.qiniu.com/docs/v6/api/reference/rs/delete.html
	 */
	public function delete($path){
		$key = $this->applyPathPrefix($path);

		/** @var $error \Qiniu\Http\Error */
		$error = $this->bucketManager->delete(
			$this->config['bucket'], $key
		);
		if($error !== null){
			$this->lastError = $error->message();
			return false;
		}

		return true;
	}

	/**
	 * @inheritDoc
	 */
	public function deleteDir($dirname){
		return true;
	}

	/**
	 * @inheritDoc
	 */
	public function createDir($dirname, Config $config){
		$key = $this->applyPathPrefix($dirname);
		return ['path' => $key, 'type' => 'dir'];
	}

	/**
	 * @inheritDoc
	 * @link http://developer.qiniu.com/docs/v6/api/reference/rs/list.html
	 */
	public function listContents($directory = '', $recursive = false){
		$directory = $this->applyPathPrefix($directory);

		/** @var $error \Qiniu\Http\Error */
		list($result, $error) = $this->bucketManager->listFiles(
			$this->config['bucket'],
			$directory
		);
		if($error !== null){
			$this->lastError = $error->message();
			return null;
		}

		$list = [];
		$items = isset($result['items']) ? $result['items'] : [];
		foreach($items as $file){
			$list[] = $this->normalizeFileInfo($file);
		}

		return $list;
	}

	/**
	 * @inheritDoc
	 * @link http://developer.qiniu.com/docs/v6/api/reference/rs/stat.html
	 */
	public function getMetadata($path){
		$key = $this->applyPathPrefix($path);

		return $this->fileInfoPool->get($key, function($key){
			/** @var $error \Qiniu\Http\Error */
			list($result, $error) = $this->bucketManager->stat($this->config['bucket'], $key);
			if($error !== null){
				$this->lastError = $error->message();
				return null;
			}

			$result['key'] = $key;
			return $this->normalizeFileInfo($result);
		});
	}

	/**
	 * @param array $stats
	 * @return array
	 */
	protected function normalizeFileInfo($stats){
		$info = [
			'type'      => 'file',
			'path'      => $stats['key'],
			'timestamp' => floor($stats['putTime'] / 10000000),
			'size'      => $stats['fsize'],
			'mimetype'  => $stats['mimeType'],
			'imageInfo' => isset($stats['imageInfo']) ? $stats['imageInfo'] : null,
		];

		if(isset($this->config['domain'])){
			$domain = rtrim($this->config['domain'], "/");
			$info['url'] = $domain."/".$stats['key'];
		}

		return $info;
	}

}
